package tech.waterism.service;

import cn.hutool.core.util.BooleanUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.waterism.constant.DispatchConstants;
import tech.waterism.constant.StationTypeConstans;
import tech.waterism.document.DPrincipleCustomDocument;
import tech.waterism.document.DispatchMoeaDocument;
import tech.waterism.dto.*;
import tech.waterism.enums.CalTypeEnum;
import tech.waterism.enums.FieldEnum;
import tech.waterism.enums.PrincipleLogicEnum;
import tech.waterism.enums.TimeTypeEnum;
import tech.waterism.utils.ForecastDispatchUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ForecastDispatchService {
    @Autowired
    private DispatchService dispatchService;
    @Autowired
    private ForecastDispatchUtils forecastDispatchUtils;

    /**
     * 调度
     */
    @SneakyThrows
    public List<TopoDataDto> calculateV2(TopoDataDto dto) {
        // 按当前出流算第一次
        dto.setDispatchType(DispatchConstants.DISPATCH_PLAN_TYPE_BEGINOUTFLOW);
        List<TopoDataDto> topoDataDtos = dispatchService.calculate(dto);
        TopoDataDto tempResult = topoDataDtos.get(0);
        if (tempResult.getTopoObject().stream().allMatch(i -> i.getFpIndex().getPrincipleId() == null)) {
            return topoDataDtos;
        }
        TopoCalDto topoCalDto = new TopoCalDto();
        BeanUtils.copyProperties(dto, topoCalDto);
        List<TopoIndexDto> topoIndexDtos = new LinkedList<>();
        for (int i = 0; i < dto.getTopoObject().size(); i++) {
            TopoObjectDto index = dto.getTopoObject().get(i);
            FPIndexDto fpIndex = index.getFpIndex();
            if (fpIndex.getPrincipleId() != null && fpIndex.getPrincipleCustom() == null) {
                DPrincipleCustomDocument principleCustomDocument =
                        dPrincipleCustomService.getById(fpIndex.getPrincipleId());
                if (principleCustomDocument != null) {
                    fpIndex.setPrincipleCustom(principleCustomDocument);
                }
            }
            TopoIndexDto topoIndexDto = new TopoIndexDto();
            fpIndex.setOrdnum(i);
            fpIndex.setOutFlowType(dto.getOutFlowType());
            topoIndexDto.setFpIndex(fpIndex);
            topoIndexDto.setFpInputs(index.getFpInputs());
            topoIndexDtos.add(topoIndexDto);
        }
        topoCalDto.setIndices(topoIndexDtos);
        // 规则调度，调用时使用赋值的形式，计算直接更改数据，不需要赋值回去
        calByCustomPrinciple(topoCalDto);
        dto.setRevOptUuidList(topoCalDto.getRevOptUuidList());
        dto.setRuleMessage(topoCalDto.getMsg());
        dto.setDispatchType(DispatchConstants.DISPATCH_PLAN_TYPE_DISPATCHRULES_CUSTOM);
        return Collections.singletonList(dto);
    }

    /**
     * 调用时使用赋值的形式，计算直接更改数据，结果不需要赋值回去
     */
    @SneakyThrows
    public void calByCustomPrinciple(TopoCalDto topoCalDto) {
        if (!checkDefaultStcd(topoCalDto)) {
            throw new Exception("请先设置缺省站");
        }
        topoCalDto.setRevOptUuidList(new LinkedList<>());
        Date dtOperate = topoCalDto.getFymdh();
        List<Date> dates =
                topoCalDto.getIndices().get(0).getFpIndex().getData().stream().map(ForecastDataDto::getDt).toList();
        int timeNumOperate = dates.indexOf(dtOperate);
        Map<String, TopoIndexDto> topoIndexDtoMap = topoCalDto.getIndices().stream()
                .collect(Collectors.toMap(i -> i.getFpIndex().getStcd(), v -> v, (k1, k2) -> k1));
        boolean loopFlg = true;
        String recordNo = "";
        while (loopFlg) {
            PrincipleRecordUsed record = setValueByCondition(topoCalDto, timeNumOperate, topoIndexDtoMap);
            if (recordNo.equals(record.getRecordNo())) {
                topoCalDto.setMsg("重复计算同一条记录，请检查计算条件:" + recordNo);
                return;
            } else if (record.getRecordNo() != null) {
                recordNo = record.getRecordNo();
            }
            if (record.getLoop()) {
                // 当前仅适配统计值，计算全局
                if (record.getCalType() != null && record.getCalType().equals(CalTypeEnum.REV_OPT)) {
                    // 构造反向优化结构
                    TopoDataOptimizeDto dto = generOpt(record, topoIndexDtoMap, dtOperate);
                    BeanUtils.copyProperties(topoCalDto, dto);
                    List<TopoObjectDto> topoObject = new LinkedList<>();
                    for (TopoIndexDto index : topoCalDto.getIndices()) {
                        TopoObjectDto item = new TopoObjectDto();
                        BeanUtils.copyProperties(index, item);
                        item.setDispatchType(DispatchConstants.DISPATCH_PLAN_TYPE_OPTIMIZEDOUTFLOW);
                        topoObject.add(item);
                    }
                    dto.setTopoObject(topoObject);
                    DispatchMoeaDocument optimizeDocument = calibrationDispatchService.calculateRule(dto);
                    topoCalDto.getRevOptUuidList().add(optimizeDocument.getUuid());
                    setValueByWaterBalance(topoCalDto, timeNumOperate, topoIndexDtoMap);
                }
            } else {
                loopFlg = false;
            }
        }
        setValueByWaterBalance(topoCalDto, timeNumOperate, topoIndexDtoMap);
    }

    private boolean checkDefaultStcd(TopoCalDto topoCalDto) {
        for (TopoIndexDto index : topoCalDto.getIndices()) {
            if (index.getFpIndex().getPrincipleCustom() == null) {
                continue;
            }
            DPrincipleCustomDocument principleCustom = index.getFpIndex().getPrincipleCustom();
            for (PrincipleRecord record : principleCustom.getRecords()) {
                if (record.getControls().stream()
                        .anyMatch(i -> BooleanUtil.isTrue(i.getSetStcd()) && i.getStcd() == null)) {
                    return false;
                }
                if (record.getConditions() != null && record.getConditions().stream()
                        .anyMatch(i -> BooleanUtil.isTrue(i.getSetStcd()) && i.getStcd() == null)) {
                    return false;
                }
            }
        }
        return true;
    }

    @SneakyThrows
    private void setValueByWaterBalance(TopoCalDto topoCalDto, int timeNumOperate,
                                        Map<String, TopoIndexDto> topoIndexDtoMap) {
        for (int i = 0; i < topoCalDto.getIndices().size(); i++) {
            TopoIndexDto index = topoCalDto.getIndices().get(i);
            FPIndexDto fpIndex = index.getFpIndex();
            if (!StationTypeConstans.RSVR_SET.contains(fpIndex.getOutcd())) {
                continue;
            }
            if (fpIndex.getPrincipleCustom() != null) {
                // 是否执行了水量平衡
                boolean calFlg = false;
                for (int timeNum = 0; timeNum < fpIndex.getData().size(); timeNum++) {
                    boolean calItemFlg = false;
                    ForecastDataDto datum = fpIndex.getData().get(timeNum);
                    if (timeNum <= timeNumOperate) {
                        continue;
                    }
                    LogFieldDto logFieldDto = new LogFieldDto().setStcd(fpIndex.getStcd()).setStnm(fpIndex.getStnm());
                    for (PrincipleRecord record : fpIndex.getPrincipleCustom().getRecords()) {
                        // 只做水量平衡
                        if (!record.getCalType().equals(CalTypeEnum.WATERBALANCE)) {
                            continue;
                        }
                        if (StringUtils.isNotBlank(record.getParentRecordNo()) &&
                                !record.getParentRecordNo().equals(fpIndex.getRecordNo())) {
                            continue;
                        }
                        Date curDate = datum.getDt();
                        if (BooleanUtil.isTrue(record.getWithRange())) {
                            if (!forecastDispatchUtils.inCalTime(record, curDate)) {
                                continue;
                            }
                        }
                        if (Boolean.TRUE.equals(record.getWithCondition())) {
                            if (record.getLogic() == null) {
                                record.setLogic(PrincipleLogicEnum.AND);
                            }
                            switch (record.getLogic()) {
                                case OR -> {
                                    for (PrincipleCondition condition : record.getConditions()) {
                                        FPIndexDto conditionIndex =
                                                getConditionIndex(condition, topoIndexDtoMap, fpIndex);
                                        if (forecastDispatchUtils.getCondition(condition, logFieldDto, conditionIndex,
                                                timeNum, timeNumOperate)) {
                                            forecastDispatchUtils.setValue(record, logFieldDto, fpIndex, timeNum,
                                                    timeNumOperate);
                                            calFlg = true;
                                            calItemFlg = true;
                                            fpIndex.setRecordNo(record.getRecordNo());
                                            break;
                                        }
                                    }
                                }
                                case AND -> {
                                    boolean conditionFlag = true;
                                    for (PrincipleCondition condition : record.getConditions()) {
                                        FPIndexDto conditionIndex =
                                                getConditionIndex(condition, topoIndexDtoMap, fpIndex);
                                        boolean curCondition =
                                                forecastDispatchUtils.getCondition(condition, logFieldDto,
                                                        conditionIndex, timeNum, timeNumOperate);
                                        conditionFlag = conditionFlag && curCondition;
                                    }
                                    if (conditionFlag) {
                                        forecastDispatchUtils.setValue(record, logFieldDto, fpIndex, timeNum,
                                                timeNumOperate);
                                        calFlg = true;
                                        calItemFlg = true;
                                        fpIndex.setRecordNo(record.getRecordNo());
                                    }
                                }
                                default -> {
                                    throw new Exception("Unexpected value: " + record.getLogic().getName());
                                }
                            }
                        } else {
                            forecastDispatchUtils.setValue(record, logFieldDto, fpIndex, timeNum, timeNumOperate);
                            calFlg = true;
                            calItemFlg = true;
                            fpIndex.setRecordNo(record.getRecordNo());
                        }
                    }
                    if (!calItemFlg) {
                        // 清空前序计算条件
                        fpIndex.setRecordNo(null);
                        // 不计算的时候，进行平衡计算
                        forecastDispatchUtils.normalWaterbalance(fpIndex, datum, timeNum);
                    }
                }
                if (calFlg && i < topoCalDto.getIndices().size() - 1) {
                    // 有赋值且不是最后一个节点
                    String reCalStcd = topoCalDto.getIndices().get(i).getFpIndex().getStcd();
                    AreaDataDto areaDataDto = new AreaDataDto();
                    BeanUtils.copyProperties(topoCalDto, areaDataDto);
                    areaDataDto.setReCalStcd(reCalStcd);
                    List<AreaIndexDto> areaIndexDtos = new LinkedList<>();
                    for (TopoIndexDto topoCalDtoIndex : topoCalDto.getIndices()) {
                        AreaIndexDto areaIndexDto = new AreaIndexDto();
                        areaIndexDto.setFpIndex(topoCalDtoIndex.getFpIndex());
                        areaIndexDto.setFpInputs(topoCalDtoIndex.getFpInputs());
                        areaIndexDtos.add(areaIndexDto);
                    }
                    areaDataDto.setIndices(areaIndexDtos);
                    // 从当前站点做预报，向下演进
                    areaService.getForecastNormal(areaDataDto, true, false);
                }
            }
        }
    }

    @SneakyThrows
    private PrincipleRecordUsed setValueByCondition(TopoCalDto topoCalDto, int timeNumOperate,
                                                    Map<String, TopoIndexDto> topoIndexDtoMap) {
        PrincipleRecordUsed result = new PrincipleRecordUsed();
        result.setLoop(false);
        //
        List<String> downStcds;
        Map<String, FloodCharacterDto> characterDtoMap = new HashMap<>();
        List<Date> dates =
                topoCalDto.getIndices().get(0).getFpIndex().getData().stream().map(ForecastDataDto::getDt).toList();
        for (TopoIndexDto index : topoCalDto.getIndices()) {
            characterDtoMap.put(index.getFpIndex().getStcd(), index.getFpIndex().getFloodCharacter());
            // 枢纽
            if (index.getFpIndex().getHbcd() != null) {
                // 如果枢纽存在，先配置出流数据map
                Map<String, List<ForecastDataDto>> replaceInputdataMap = new HashMap<>();
                if (index.getFpIndex().getHubDisData() != null) {
                    replaceInputdataMap = index.getFpIndex().getHubDisData();
                }
                for (DHubGateDto dHubGateDto : index.getFpIndex().getHub().getGate()) {
                    if (replaceInputdataMap.containsKey(dHubGateDto.getDtcd())) {
                        continue;
                    }
                    List<ForecastDataDto> emptyList = ForecastDispatchUtils.getEmptyList(dates);
                    replaceInputdataMap.put(dHubGateDto.getDtcd(), emptyList);
                }
                if (index.getFpIndex().getHubDisData() == null) {
                    index.getFpIndex().setHubDisData(replaceInputdataMap);
                }
            }
        }
        // 节点顺序执行
        for (int topoIndex = 0; topoIndex < topoCalDto.getIndices().size(); topoIndex++) {
            TopoIndexDto index = topoCalDto.getIndices().get(topoIndex);
            FPIndexDto fpIndex = index.getFpIndex();
            if (fpIndex.getPrincipleCustom() != null) {
                // 执行规则调度
                downStcds = topoCalDto.getIndices().stream().map(item -> item.getFpIndex().getStcd()).toList()
                        .subList(topoIndex + 1, topoCalDto.getIndices().size());
                boolean stillFlg = false;
                // 枢纽是否作为调度规则
                boolean hubFlg = fpIndex.getPrincipleCustom().getHbcd() != null;
                // 按时间顺序执行
                for (int timeNum = timeNumOperate + 1; timeNum < fpIndex.getData().size(); timeNum++) {
                    ForecastDataDto datum = fpIndex.getData().get(timeNum);
                    LogFieldDto logFieldDto = new LogFieldDto().setStcd(fpIndex.getStcd()).setStnm(fpIndex.getStnm());
                    boolean calFlg = false;
                    for (PrincipleRecord record : fpIndex.getPrincipleCustom().getRecords()) {
                        if (calFlg) {
                            // 别的record算过就不需要再判断了
                            break;
                        }
                        if (StringUtils.isNotBlank(record.getParentRecordNo()) &&
                                !record.getParentRecordNo().equals(fpIndex.getRecordNo())) {
                            // 上一个时刻执行过ParentRecord，才会执行这个赋值
                            continue;
                        }
                        // 排洪前置条件在最开始判断
                        if (record.getCalType().equals(CalTypeEnum.RULE_DSC)) {
                            if (fpIndex.getData().stream().filter(item -> item.getDt().before(datum.getDt()))
                                    .noneMatch(item -> item.getIGrade() >= 1)) {
                                // 如果没有执行过分洪，那么也不能排洪
                                continue;
                            }
                        }
                        if (Objects.nonNull(record.getParentRecordNoLogics()) &&
                                !record.getParentRecordNoLogics().isEmpty()) {
                            boolean flg =
                                    record.getParentRecordNoLogics().get(0).getLogic().equals(PrincipleLogicEnum.AND);
                            boolean flgT = false;
                            // 前序中数据执行过Record，才会执行这个赋值
                            for (ParentRecordNoLogic parentRecordNoLogic : record.getParentRecordNoLogics()) {
                                for (int i1 = timeNumOperate; i1 < timeNum; i1++) {
                                    ForecastDataDto fpIndexDatum = fpIndex.getData().get(i1);
                                    if (parentRecordNoLogic.getRecordNo().equals(fpIndexDatum.getRecordNo())) {
                                        flgT = true;
                                        break;
                                    }
                                }
                                switch (parentRecordNoLogic.getLogic()) {
                                    case AND -> flg = flgT & flg;
                                    case OR -> flg = flgT | flg;
                                    case NOT -> flg = flg & !flgT;
                                }
                                flgT = false;
                            }
                            if (!flg) {
                                continue;
                            }
                        }
                        if (record.getCalType().equals(CalTypeEnum.CTL_COND) ||
                                record.getCalType().equals(CalTypeEnum.WATERBALANCE)) {
                            // 水量平衡，不在这个逻辑里赋值
                            // 控泄条件，作为反算中的条件，不在这个逻辑里赋值
                            continue;
                        }
                        Date curDate = datum.getDt();
                        if (BooleanUtil.isTrue(record.getWithRange())) {
                            if (!forecastDispatchUtils.inCalTime(record, curDate)) {
                                stillFlg = false;
                                continue;
                            }
                        }
                        if (Boolean.TRUE.equals(record.getWithCondition())) {
                            if (record.getLogic() == null) {
                                record.setLogic(PrincipleLogicEnum.AND);
                            }
                            switch (record.getLogic()) {
                                case OR -> {
                                    for (PrincipleCondition condition : record.getConditions()) {
                                        // 单站比较
                                        FPIndexDto conditionIndex =
                                                getConditionIndex(condition, topoIndexDtoMap, fpIndex);
                                        logFieldDto = new LogFieldDto().setStcd(conditionIndex.getStcd())
                                                .setStnm(conditionIndex.getStnm());
                                        if (stillFlg || condition.getCombConditions() == null &&
                                                forecastDispatchUtils.getCondition(condition, logFieldDto,
                                                        conditionIndex, timeNum, timeNumOperate) ||
                                                condition.getCombConditions() != null &&
                                                        forecastDispatchUtils.getCombCondition(topoCalDto, condition,
                                                                timeNum, timeNumOperate)) {
                                            // 满足条件 直接赋值
                                            setValue(hubFlg, fpIndex, timeNum, record, characterDtoMap, topoCalDto,
                                                    timeNumOperate, result, downStcds, logFieldDto);
                                            if (Boolean.TRUE.equals(result.getSetValueReturn())) {
                                                return result;
                                            }
                                            if (TimeTypeEnum.AFTER.equals(condition.getTime().getType())) {
                                                // 发生后，不再比较其余条件
                                                stillFlg = true;
                                            }
                                            calFlg = true;
                                            fpIndex.setRecordNo(record.getRecordNo());
                                            break;
                                        }
                                    }
                                }
                                case AND -> {
                                    boolean conditionFlag = true;
                                    for (PrincipleCondition condition : record.getConditions()) {
                                        boolean curCondition;
                                        if (condition.getCombConditions() != null) {
                                            //  组合条件
                                            curCondition = forecastDispatchUtils.getCombCondition(topoCalDto, condition,
                                                    timeNum, timeNumOperate);
                                        } else {
                                            // 单站比较
                                            FPIndexDto conditionIndex =
                                                    getConditionIndex(condition, topoIndexDtoMap, fpIndex);
                                            logFieldDto = new LogFieldDto().setStcd(conditionIndex.getStcd())
                                                    .setStnm(conditionIndex.getStnm());
                                            curCondition = forecastDispatchUtils.getCondition(condition, logFieldDto,
                                                    conditionIndex, timeNum, timeNumOperate);
                                        }
                                        if (TimeTypeEnum.AFTER.equals(condition.getTime().getType()) && curCondition) {
                                            // 发生后，这个条件始终是true
                                            stillFlg = true;
                                        }
                                        if (TimeTypeEnum.AFTER.equals(condition.getTime().getType()) && stillFlg) {
                                        } else {
                                            conditionFlag = conditionFlag && curCondition;
                                        }
                                        if (!conditionFlag) {
                                            break;
                                        }
                                    }
                                    if (conditionFlag) {
                                        setValue(hubFlg, fpIndex, timeNum, record, characterDtoMap, topoCalDto,
                                                timeNumOperate, result, downStcds, logFieldDto);
                                        if (Boolean.TRUE.equals(result.getSetValueReturn())) {
                                            return result;
                                        }
                                        calFlg = true;
                                        fpIndex.setRecordNo(record.getRecordNo());
                                    }
                                }
                                default -> {
                                    throw new Exception("Unexpected value: " + record.getLogic().getName());
                                }
                            }
                        } else {
                            // 不需要条件，直接赋值
                            setValue(hubFlg, fpIndex, timeNum, record, characterDtoMap, topoCalDto, timeNumOperate,
                                    result, downStcds, logFieldDto);
                            calFlg = true;
                            fpIndex.setRecordNo(record.getRecordNo());
                        }
                    }
                    if (!calFlg) {
                        // 清空前序计算条件
                        fpIndex.setRecordNo(null);
                        if (StationTypeConstans.RSVR_SET.contains(fpIndex.getOutcd())) {
                            // 不计算的时候，进行平衡计算
                            forecastDispatchUtils.normalWaterbalance(fpIndex, datum, timeNum);
                        }
                    }
                }
            }
            if (fpIndex.getData().stream().anyMatch(item -> Boolean.TRUE.equals(item.getUsePrinciple())) &&
                    topoIndex < topoCalDto.getIndices().size() - 1) {
                // 有赋值且不是最后一个节点
                String reCalStcd = topoCalDto.getIndices().get(topoIndex).getFpIndex().getStcd();
                AreaDataDto areaDataDto = new AreaDataDto();
                BeanUtils.copyProperties(topoCalDto, areaDataDto);
                areaDataDto.setReCalStcd(reCalStcd);
                List<AreaIndexDto> areaIndexDtos = new LinkedList<>();
                for (TopoIndexDto topoCalDtoIndex : topoCalDto.getIndices()) {
                    AreaIndexDto areaIndexDto = new AreaIndexDto();
                    areaIndexDto.setFpIndex(topoCalDtoIndex.getFpIndex());
                    areaIndexDto.setFpInputs(topoCalDtoIndex.getFpInputs());
                    areaIndexDtos.add(areaIndexDto);
                }
                areaDataDto.setIndices(areaIndexDtos);
                // 从当前站点做预报，向下演进
                areaService.getForecastNormal(areaDataDto, true, false);
            }
        }
        return result;
    }

    @SneakyThrows
    private void setValue(boolean hubFlg, FPIndexDto fpIndex, int timeNum, PrincipleRecord record,
                          Map<String, FloodCharacterDto> characterDtoMap, TopoCalDto topoCalDto, int timeNumOperate,
                          PrincipleRecordUsed result, List<String> downStcds, LogFieldDto logFieldDto) {
        if (record.getCalType().equals(CalTypeEnum.RULE_DVR)) {
            // 分洪的情况
            forecastDispatchUtils.setDvrValue(record, topoCalDto, fpIndex, timeNum);
        } else if (record.getCalType().equals(CalTypeEnum.RULE_DSC)) {
            // 排洪的情况
            forecastDispatchUtils.setDscValue(record, topoCalDto, fpIndex, timeNum);
        } else if (hubFlg) {
            // 枢纽的情况
            forecastDispatchUtils.setDisValue(fpIndex, timeNum, record, characterDtoMap);
        } else {
            if (record.getCalType().equals(CalTypeEnum.REV_OPT)) {
                BeanUtils.copyProperties(record, result);
                result.setCtlConds(fpIndex.getPrincipleCustom().getRecords().stream()
                        .filter(item -> item.getCalType().equals(CalTypeEnum.CTL_COND)).toList());
                result.setStcd(fpIndex.getStcd());
                result.setLoop(true);
                // 如果是反算，直接退出赋值，进行反算
                result.setSetValueReturn(true);
                return;
            } else if (calAllNodes(record, downStcds)) {
                // 如果需要反过来计算上游，loop字段赋值true
                result.setLoop(true);
            }
            forecastDispatchUtils.setValue(record, logFieldDto, fpIndex, timeNum, timeNumOperate);
        }
    }

    @SneakyThrows
    private FPIndexDto getConditionIndex(PrincipleCondition condition, Map<String, TopoIndexDto> topoIndexDtoMap,
                                         FPIndexDto fpIndex) {
        FPIndexDto fpIndexCal;
        if (condition.getStcd() == null) {
            fpIndexCal = fpIndex;
        } else if (condition.getStcd().contains("SN")) {
            fpIndexCal = fpIndex;
            fpIndexCal.getData().forEach(i -> i.setQIn(i.getQSim()));
        } else if (topoIndexDtoMap.containsKey(condition.getStcd())) {
            fpIndexCal = topoIndexDtoMap.get(condition.getStcd()).getFpIndex();
        } else if (topoIndexDtoMap.containsKey(condition.getStcdR())) {
            fpIndexCal = topoIndexDtoMap.get(condition.getStcdR()).getFpIndex();
        } else {
            throw new Exception("相关测站不在结构中：" + condition.getStcd());
        }
        return fpIndexCal;
    }

    /**
     * 构造反向优化结构
     */
    @SneakyThrows
    private TopoDataOptimizeDto generOpt(PrincipleRecordUsed record, Map<String, TopoIndexDto> topoIndexDtoMap,
                                         Date fymdh) {
        TopoDataOptimizeDto dto = new TopoDataOptimizeDto();
        String stcd = record.getStcd();
        String algorithmStr = """
                {
                "name":"OMOPSO",
                "maxEvaluations":100,
                "attributes":{"populationSize":"100","epsilon":"0.0001"}
                }
                """;
        AlgorithmDispatch algorithm = JSONObject.parseObject(algorithmStr, AlgorithmDispatch.class);
        if (record.getAlgorithm() != null) {
            AlgorithmDispatch algorithmCustom = record.getAlgorithm();
            if (algorithmCustom.getMaxEvaluations() != null) {
                algorithm.setMaxEvaluations(algorithmCustom.getMaxEvaluations());
            }
            if (algorithmCustom.getAttributes().containsKey("populationSize")) {
                algorithm.getAttributes().put("populationSize", algorithmCustom.getAttributes().get("populationSize"));
            }
        }
        // variables
        List<DispatchVariable> variables = new LinkedList<>();
        String variableStr = """
                {
                  "compareField": "QOUT",
                  "lowerBound": {
                    "valueType": "FIXED",
                    "value": 0.0
                  },
                  "upperBound": {
                    "valueType": "FIXED",
                    "value": 0.0
                  },
                  "segment": 7,
                  "variableType": "real"
                }
                """;
        DispatchVariable variable = JSONObject.parseObject(variableStr, DispatchVariable.class);
        variable.setStcd(stcd);
        List<ForecastDataDto> data = topoIndexDtoMap.get(stcd).getFpIndex().getData();
        double qinMax = data.stream().mapToDouble(ForecastDataDto::getQIn).max().getAsDouble();
        for (PrincipleControl control : record.getControls()) {
            if (stcd.equals(control.getStcd()) && control.getCompareField().equals(FieldEnum.QOUT) &&
                    control.getValue() != null) {
                double qCompare = control.getValue();
                if (qinMax < qCompare) {
                    qinMax = qCompare;
                }
            }
        }
        variable.getUpperBound().setValue(qinMax);
        variables.add(variable);
        // objectives
        List<DispatchObjective> objectives = new LinkedList<>();
        for (int i = 0; i < record.getControls().size(); i++) {
            PrincipleControl control = record.getControls().get(i);
            DispatchObjective objective = new DispatchObjective();
            if (topoIndexDtoMap.containsKey(control.getStcd())) {
                objective.setStcd(control.getStcd());
            } else if (topoIndexDtoMap.containsKey(control.getStcdR())) {
                objective.setStcd(control.getStcdR());
            } else {
                throw new Exception("控制站点不在结构中，请检查：" + control.getStcd());
            }
            objective.setObjective("-");
            FieldEnum compareField = control.getCompareField();
            if (compareField.equals(FieldEnum.Z_END)) {
                compareField = FieldEnum.RZ;
            }
            objective.setCompareField(compareField);
            objective.setWeight(0.2 * (i + 1));
            objectives.add(objective);
        }
        // constrains
        List<DispatchConstraint> constrains = new LinkedList<>();
        for (PrincipleControl control : record.getControls()) {
            DispatchConstraint constraint = new DispatchConstraint();
            if (topoIndexDtoMap.containsKey(control.getStcd())) {
                constraint.setStcd(control.getStcd());
            } else if (topoIndexDtoMap.containsKey(control.getStcdR())) {
                constraint.setStcd(control.getStcdR());
            } else {
                throw new Exception("控制站点不在结构中，请检查：" + control.getStcd());
            }
            constraint.setCompareField(control.getCompareField());
            constraint.setLogic(control.getLogic());
            constraint.setValueType(control.getValueType());
            constraint.setValue(control.getValue());
            constraint.setCatalog(control.getCatalog());
            constrains.add(constraint);
        }
        //
        if (record.getCtlConds() != null && !record.getCtlConds().isEmpty()) {
            for (PrincipleRecord principleRecord : record.getCtlConds()) {
                DispatchConstraint constraint = new DispatchConstraint();
                constraint.setStcd(stcd);
                constraint.setCompareField(FieldEnum.CTL_COND);
                constraint.setCtlCond(principleRecord);
                constrains.add(constraint);
            }
        }
        //
        algorithm.setVariables(variables);
        algorithm.setObjectives(objectives);
        algorithm.setConstrains(constrains);
        dto.setAlgorithm(algorithm);
        return dto;
    }

    /**
     * 是否受下游控制
     */
    private boolean calAllNodes(PrincipleRecord record, List<String> downStcds) {
        boolean result = false;
        if (record.getConstrains() != null) {
            result = result || record.getConstrains().stream().filter(i -> i.getStcd() != null)
                    .anyMatch(i -> downStcds.contains(i.getStcd()));
        }
        if (record.getControls() != null) {
            result = result || record.getControls().stream().filter(i -> i.getStcd() != null)
                    .anyMatch(i -> downStcds.contains(i.getStcd()));
        }
        return result;
    }
}
